/*
 * Copyright (c) 2006,2007
 *                    Antti Siira <antti@utu.fi>
 *                    Eino Tuominen <eino@utu.fi>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "srvutils.h"
#include "sha256.h"

void (*p_convert_int64_to_big_endian)(sha_ulong_t *num) = &convert_int64_big_endian;
void (*p_convert_int32_to_big_endian)(sha_uint_t *num) = &convert_int32_big_endian;

/* 232 times the square root of the first 8 primes 2..19 */
const sha_256_t DEFAULT_SHA256 = {
  0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };

/* 232 times the cube root of the first 64 primes 2..311 */
unsigned int ROUND_CONSTANTS[64] = {
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 };

/* Rotate bits right eg. right 1 bit == 10100100 => 01010010 */
sha_uint_t rotate_right(sha_uint_t num, int amount) {
  return (num >> amount) | (num << (32 - amount));
}

/* Return TRUE if little endian. False otherwise. */
int little_endian() {
  int i = 1;
  char *p = (char *) &i;
  /* Lowest address contains the least significant byte */
  if (p[0] == 1)
    return TRUE;
  else
    return FALSE;
}

void swap_bytes(sha_byte_t* a, sha_byte_t* b) {
  sha_byte_t tmp = *a;
  *a = *b;
  *b = tmp;
}

void convert_int64_little_endian (sha_ulong_t *num) {
  sha_byte_t* index = (sha_byte_t*)num;

  swap_bytes(index+0, index+7);
  swap_bytes(index+1, index+6);
  swap_bytes(index+2, index+5);
  swap_bytes(index+3, index+4);
}

void convert_int64_big_endian (sha_ulong_t *num) {
  do { num = num; } while (0);
}

void convert_int32_little_endian (sha_uint_t *num) {
  sha_byte_t* index = (sha_byte_t*)num;

  swap_bytes(index+0, index+3);
  swap_bytes(index+1, index+2);
}

void convert_int32_big_endian (sha_uint_t *num) {
  do { num = num; } while (0);
}

void debug_print_digest(sha_256_t digest, int with_newline) {
  printf("sha256: %08x %08x %08x %08x %08x %08x %08x %08x", digest.h0, digest.h1, digest.h2, digest.h3, digest.h4, digest.h5, digest.h6, digest.h7);
  if ( with_newline ) printf("\n");
}

sha_256_t sha256(sha_byte_t* message, sha_ulong_t size) {
  sha_256_t digest = DEFAULT_SHA256;
  sha_256_t tmp_digest = DEFAULT_SHA256;

  sha_ulong_t new_size = size + 1 + 8 + ((448 - (((sha_ulong_t) 8) * (size + 1))) % 512) / 8;
  sha_ulong_t tmp_size = size * 8;
  /* Multipurpose indices */
  sha_uint_t i, j;
  sha_byte_t* iter;
  sha_uint_t w[64];
  sha_uint_t s0, s1, maj, t2, ch, t1, chunk;
  
  sha_byte_t* digestable_message = (sha_byte_t *)Malloc(sizeof(sha_byte_t)*new_size);

  if ( little_endian() ) {
    p_convert_int64_to_big_endian = &convert_int64_little_endian;
    p_convert_int32_to_big_endian = &convert_int32_little_endian;
  }

  /* Initialize buffer */
  bzero(digestable_message, new_size);
  memcpy((void*)digestable_message, (const void*)message, size);
  digestable_message[size] = 0x80;
  
  /*
   * Insert message bit length to the end of the buffer
   * in big endian 64 integer
   */
  (*p_convert_int64_to_big_endian)(&tmp_size);
  for ( i=new_size - 8, j=0, iter=(sha_byte_t*)&tmp_size ; i<new_size ; i++, j++ ) {
    *(digestable_message+i) = *(iter+j);
  }

  iter = (sha_byte_t*)digestable_message;

  
  for ( i = 0 ; i < new_size ; i += 64 ) {

    /* Initialize the beginning 0..15 of the word block */
    for ( j = 0 ; j < 64 ; j += 4 ) {
      chunk = *((sha_uint_t*)(iter+i+j));
      p_convert_int32_to_big_endian(&chunk);
      w[j/4] = chunk;
    }

    /* Initialize the end 16..63 of the word block */
    for (j = 16 ; j < 64 ; j++) {
      s0 = rotate_right(w[j-15], 7) ^ rotate_right(w[j-15], 18) ^ (w[j-15] >> 3);
      s1 = rotate_right(w[j-2], 17) ^ rotate_right(w[j-2], 19) ^ (w[j-2] >> 10);
      w[j] = w[j-16] + s0 + w[j-7] + s1;
    }

    /* Init the temporary digest */
    tmp_digest = digest;

    /*  Main loop */
    for ( j=0 ; j<64 ; j++ ) {
      s0 = rotate_right(tmp_digest.h0, 2) ^ rotate_right(tmp_digest.h0, 13) ^ rotate_right(tmp_digest.h0, 22);
      maj = (tmp_digest.h0 & tmp_digest.h1) ^ (tmp_digest.h1 & tmp_digest.h2) ^ (tmp_digest.h2 & tmp_digest.h0);      
      t2 = s0 + maj;
      s1 = rotate_right(tmp_digest.h4, 6) ^ rotate_right(tmp_digest.h4, 11) ^ rotate_right(tmp_digest.h4, 25);
      ch = (tmp_digest.h4 & tmp_digest.h5) ^ ((~tmp_digest.h4) & tmp_digest.h6);      
      t1 = tmp_digest.h7 + s1 + ch + ROUND_CONSTANTS[j] + w[j];

      /* Update values for next iteration */
      tmp_digest.h7 = tmp_digest.h6;
      tmp_digest.h6 = tmp_digest.h5;
      tmp_digest.h5 = tmp_digest.h4;
      tmp_digest.h4 = tmp_digest.h3 + t1;
      tmp_digest.h3 = tmp_digest.h2;
      tmp_digest.h2 = tmp_digest.h1;
      tmp_digest.h1 = tmp_digest.h0;
      tmp_digest.h0 = t2 + t1;
    }
	
    /* Add this chunk's hash to result so far: */
    digest.h0 += tmp_digest.h0;
    digest.h1 += tmp_digest.h1;
    digest.h2 += tmp_digest.h2;
    digest.h3 += tmp_digest.h3;
    digest.h4 += tmp_digest.h4;
    digest.h5 += tmp_digest.h5;
    digest.h6 += tmp_digest.h6;
    digest.h7 += tmp_digest.h7;

  }

  /* Free Willy! */
  Free(digestable_message);

  return digest;
}

sha_256_t sha256_string(char* message) {
  return sha256(((sha_byte_t*)message), strlen(message));
}

